import struct,re,timeit,io,os
from utils import FileUtil,PBGenUtil,ExcelUtil,TranslateUtil,SettingUtil

from datetime import datetime

#单个sheet表格的数据和信息
class SheetInfo:
    def __init__(self,fileName,name,sheetDataDict):
        self._fileName = fileName
        self._sheetDataDict = sheetDataDict
        nameArr = name.split("#")
        self._name = nameArr[0]
        self._tableName = nameArr[1] if len(nameArr)==2 else "" #中文表名
        self._comments = []
        self._fields = []
        self._fieldTypes = []
        self._rowDatas = None
        self._isCompile = False
        self._totalBytes = None
        self._keyStr = ""
        self.__ROW_KEY_MAP__ = {}
        self.init()
    
    def init(self):
        if self._sheetDataDict != None:
            self._comments = self.toStrRow(self._sheetDataDict[0])
            self._fields = self.toStrRow(self._sheetDataDict[1])
            self._fieldTypes = self.toStrRow(self._sheetDataDict[2])
            del self._sheetDataDict[0]
            del self._sheetDataDict[1]
            del self._sheetDataDict[2]
            self.formatKey()
            
    def toStrRow(self,rows):
        return [str(r).strip() for r in rows]
    
    def formatKey(self):
        keyInfos = []
        for i in range(len(self._fields)):
            item = self._fields[i]
            temp = item.split("#")
            if len(temp)==2 and temp[1]!="":
                keyInfos.append({"keyVal":temp[0],"keyIdx":int(temp[1])})
                self._fields[i] = temp[0]
                
        keyInfos.sort(key=lambda a: a['keyIdx'])
        count = len(keyInfos)
        self._keyArr = []
        for i in range(count):
            keyVal = keyInfos[i]['keyVal']
            self._keyStr += keyVal
            self._keyArr.append(keyVal)
            if i < count-1:
                self._keyStr += "_"
                
        #print(f"formatKey格式化表格{self.name}的key={self._keyStr}")
        
    def toProtoContent(self)->str:
        return FileUtil.toProtoContent(self.toMessageStruct())
    
    def toMessageStruct(self)->str:
        content = FileUtil.getMessageTemp()
        content = content.replace(r"$name$",self._name)
        fieldStr = ""
        order = 1
        for i in range(len(self._fields)):
            field = self._fields[i]
            if field !="" and field!="None":
                ctype = self.getProtoType(self._fieldTypes[i])
                fieldStr = fieldStr+"   "+ctype + " " + self._fields[i] + " = "+ str(order) + f";//{self._comments[i]}\n"
                order += 1
        content = content.replace(r"$fileds$",fieldStr)
        content = content.strip()+"\n"
        return content
    
    def getProtoType(self,fieldType):
        if fieldType.startswith("int"):
            return "int32"
        if fieldType.startswith("float"):
            return "float"
        return "string"

    def writeProtoFile(self):
        content = self.toProtoContent()
        FileUtil.writeTextFile(self.protoFile,content)
        
    def getRowDatas(self):
        if not self._rowDatas:
            self._rowDatas = []
            keys = self._sheetDataDict.keys()
            for key in keys:
                self._rowDatas.append(self._sheetDataDict[key])
        return self._rowDatas
             
    def compile(self):
        if not self._isCompile:
            PBGenUtil.compilePB(self.name)
            self._isCompile = True
    
    #把数据转换成pb二进制格式
    def toByteArray(self):
        if not self._totalBytes:
            # self.compile()
            writerBuffer = io.BytesIO()
            writerBuffer.seek(0)
            rowDatas = self.getRowDatas()
            rowCount = len(rowDatas)
            writerBuffer.write(struct.pack("<i",rowCount)) #首先写入数据总行数
            isKey = self.writeKey(writerBuffer)
            
            colCount = len(self._fields)
            print(f"{self._name}数据总行数 count=",rowCount,"总列数=",colCount,"循环总次数=",rowCount*colCount)
            configModule = SettingUtil.getSetting().configModule
            usePB = SettingUtil.getSetting().usePB
            if not usePB: #自定义的二进制格式 需要写入字段
                self.writeFieldInfos(writerBuffer)
                
            msg = PBGenUtil.instanceMessage(PBGenUtil.sheetNameToModuleName(configModule),self.name) if usePB else None
            rowWritor = None
            for rows in rowDatas:
                if not usePB:
                    rowWritor = io.BytesIO()
                rowKey  = self.readRow(rows,usePB,msg,rowWritor,isKey)    
                if isKey and not self.verify(rowKey):
                    continue
                msgBytes = None
                msgSize = 0
                if not usePB:
                    rowWritor.flush()
                    msgBytes = rowWritor.getvalue()
                    msgSize = len(msgBytes)
                else:
                    msgBytes = msg.SerializeToString() #把单行的PB数据转换成字节数组
                    msgSize = msg.ByteSize()
                writerBuffer.write(struct.pack("<i",msgSize)) #写入单行数据长度
                writerBuffer.write(msgBytes)
                
            writerBuffer.flush()
            self._totalBytes = writerBuffer.getvalue()
            writerBuffer.close()
            rowWritor.close()
            # print("self._totalBytes len=",len(self._totalBytes))
        return self._totalBytes
    
    def writeKey(self,writerBuffer):
        keyLen = 0
        keyByte = None
        isKey = self._keyStr !=""
        if isKey:
            keyByte = self._keyStr.encode("utf8")
            keyLen = len(keyByte)
        writerBuffer.write(struct.pack("<H",keyLen)) #写入主键字节长度;2个字节 无符号整型
        if keyLen>0:
            writerBuffer.write(keyByte) #写入key
        return isKey
    
    def readRow(self,rows,usePB,msg,rowWriter,isKey):
        rowKey = ""
        filedValDic = {}
        realRowWriter = io.BytesIO() if not usePB else None
        fieldIdx = 0
        dfValFieldIdxs = ""
        unWriteDFVal = SettingUtil.getSetting().unWriteDFVal
        for val,field ,ctype in zip(rows,self._fields,self._fieldTypes):
            if field == "" or field =="None" or field ==None:
                continue
            realVal,isDFVal = self.fmtFieldDataType(val,ctype)
            if usePB:
                setattr(msg,field,realVal)
            else:
                if not isDFVal or not unWriteDFVal:
                    self.serialize(realVal,ctype,realRowWriter)
                    if isKey and field in self._keyArr:
                        filedValDic[field] = realVal
                if isDFVal and unWriteDFVal:
                    dfValFieldIdxs += str(fieldIdx) + "," #记录不写入的字段下标
            fieldIdx += 1

        if not usePB:
            realRowWriter.flush()
            if unWriteDFVal:
                print(f"{self.name} 没有值的字段 dfValFieldIdxs=",dfValFieldIdxs)
                tempBytes = dfValFieldIdxs.encode("utf8")
                tempLen = len(tempBytes)
                rowWriter.write(struct.pack("<i",tempLen))
                if tempLen>0:
                    rowWriter.write(tempBytes)
            rowWriter.write(realRowWriter.getvalue())
            realRowWriter.close()
        
        if isKey:
            if usePB:
                rowKey = PBGenUtil.getMsgKeyStr(msg,self._keyStr)
            else:
                for field in self._keyArr:
                    if field in filedValDic:
                        rowKey += str(filedValDic[field]) + "_"
                rowKey = rowKey[:-1]
                
        return rowKey
    
    def serialize(self,val,ctype,ioWritor):
        c = self.getPackType(ctype)
        if c == "<s":
            b = val.encode("utf8")
            tempLen = len(b)
            ioWritor.write(struct.pack("<i",tempLen)) #字符串要先写入长度
            if tempLen > 0:
                ioWritor.write(b)
        else:
            ioWritor.write(struct.pack("<i",int(val)))
    
    def getWriteFields(self):
        fields = [field.strip() for field in self._fields if field!=None and field.strip()!=""]
        dups = [f for f in fields if fields.count(f)>1]
        dupset = set(dups)
        if len(dupset) > 0:
            dup = ",".join(dupset)
            raise Exception(f"配置表 {self.name} 存在重复字段 {dup}")
        return fields
    
    def getWriteCtype(self):
        ctypes = []
        for field,ctype in zip(self._fields,self._fieldTypes):
            if field!=None and field.strip()!="":
                ctypes.append(self.getWriteType(ctype))
        return ctypes
    
    def writeFieldInfos(self,writerBuffer:io.BytesIO): #使用自定义二进制格式需要写入字段名，和数据类型
        fields = self.getWriteFields()
        ctypes = self.getWriteCtype()
        if len(fields) != len(ctypes):
            raise Exception(f"{self.name} 字段个数和数据类型个数不相同 fields={fields},ctypes={ctypes}")
        splitText = ","
        self.writeStr(splitText.join(fields),writerBuffer) #写入字段
        self.writeStr(splitText.join(ctypes),writerBuffer) #写入数据类型
    
    def writeStr(self,tempStr,writerBuffer:io.BytesIO):
        strByte = tempStr.encode("utf8")
        strLen = len(strByte)
        writerBuffer.write(struct.pack("<H",strLen)) #写入字符串长度 字符不能超过 65535
        writerBuffer.write(strByte)
    
    def getPackType(self,ctype):
        if ctype.startswith("int"):
            return "<i"
        elif ctype.startswith("float"):
            return "<f"
        else:
            return "<s"
    
    def fmtFieldDataType(self,val,ctype):
        isFloat = ctype == "float"
        isDFVal = False
        if ctype.startswith("int") or isFloat:
            if val==None:
                val = "0"
            val = str(val).strip()
            if not isFloat:
                valArr = re.split(r"\D",val) #去掉小数点
                val = valArr[0].strip()
            if val =="":
                val ="0"
            val = float(val) if isFloat else int(val)
            isDFVal = val == 0
        else:
            val = TranslateUtil.translate(str(val)) #只有字符串需要翻译
            isDFVal = val == "" or val==None
        return val,isDFVal
    
    def getWriteType(self,ctype):
        if ctype.startswith("int"):
            return "int"
        elif ctype.startswith("float"):
            return "float"
        else:
            return "string"

    def toMessageArray(self):
        msgArr = PBGenUtil.byteToMessageCollect(self.toByteArray(),self.moduleName,self.name)
        return msgArr
    
    def verify(self,key)->bool:
        flag = key in self.__ROW_KEY_MAP__
        if flag:
            raise Exception(f"配置表[{self._name}]存在重复的数据{self._keyStr}={key}")
        self.__ROW_KEY_MAP__[key] = True
        return not flag
    
    @property
    def protoFile(self):
        return os.path.join(SettingUtil.getSetting().protoRoot,self._name+".proto")
    
    @property
    def fileName(self):
        return self._fileName
    
    @property
    def name(self):
        return self._name
    
    @property
    def tableName(self):
        return self._tableName
    
    @property
    def moduleName(self):
        return PBGenUtil.sheetNameToModuleName(self.name)
    
